#pragma once
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "Uniform.h"

class Transform {
public:
	Transform() = default;

	Transform(const Mat& worldModelMatrix) {
		m_modelMatrix = worldModelMatrix;
	}

	const Mat& modelMatrix() {
		if (m_isGlobalDirty) {
			/**@note Global TRS matrix*/
			const auto& rotX = glm::rotate(Mat(1.0f), m_globalEulerRot.x, Vec3(1.0f, 0.0f, 0.0f));
			const auto& rotY = glm::rotate(Mat(1.0f), m_globalEulerRot.y, Vec3(0.0f, 1.0f, 0.0f));
			const auto& rotZ = glm::rotate(Mat(1.0f), m_globalEulerRot.z, Vec3(0.0f, 0.0f, 1.0f));
			const auto& translateMat = glm::translate(Mat(1.0f), m_globalPos);
			const auto& rotMat = rotY * rotX * rotZ;// Y * X * Z
			const auto& scaleMat = glm::scale(Mat(1.0f), m_globalScale);
			/**@note translation * rotation * scale (also known as TRS matrix)*/
			m_globalModelMatrix = translateMat * rotMat * scaleMat;
		}
		if (m_isDirty || m_isGlobalDirty || m_isGlobalRotAroundDirty) {
			m_finalModelMatrix = m_globalRotAroundMatrix * m_globalModelMatrix * m_modelMatrix;
			m_isDirty = m_isGlobalDirty = m_isGlobalRotAroundDirty = false;
		}
		if (m_parentTransform) {
			m_modelMatrixWithParent = m_parentTransform->modelMatrix() * m_finalModelMatrix;
			return m_modelMatrixWithParent;
		}
		return m_finalModelMatrix;
	}

	void setParentTransform(Transform* parentTransform) { m_parentTransform = parentTransform; }

	Mat getMatOfRotAroundCenter(const Vec3& rawCenter, float eulerRot, const Vec3& axis = Vec3(0.0, 1.0, 0.0)) {
		const auto& aroundCenter = rawCenter;// Vec3(modelMatrix() * Vec4(rawCenter, 1.0));
		const auto& translateMat = glm::translate(Mat(1.0), -aroundCenter);// Transfer to the origin (0,0,0)
		const auto& rotMat = glm::rotate(Mat(1.0), eulerRot, axis);// From the perspective of the rotation angle and the slope of the cut line, the corresponding angle must be converted
		const auto& translateMat2 = glm::translate(Mat(1.0), aroundCenter);// Movement
		return translateMat2 * rotMat * translateMat;
	}

	/**
	* @brief Whether it is around the X axis, Y axis, or Z axis, the direction of rotation follows the right hand rules.
	* That is to say, the right hand is clenched with the fist, and the thumb fingers towards the rotating shaft in the right hand, then the direction of your four fingers is positive direction. Rotate.
	* This positive, that is, the direction of the direction of the counter-point direction in the direction of the front axis (looking in the direction of the negative axis)
	*/
	void rotateAroundCenter(Vec3& point, const Vec3& rawCenter, float eulerRot, const Vec3& axis = Vec3(0.0, 1.0, 0.0)) {
		point = getMatOfRotAroundCenter(rawCenter, eulerRot, axis) * Vec4(point, 1.0);
	}

	void scaleAroundCenter(Vec3& point, const Vec3& rawCenter, const Vec3& scale3D) {
		const auto& aroundCenter = Vec3(modelMatrix() * Vec4(rawCenter, 1.0));
		const auto& translateMat = glm::translate(Mat(1.0), -aroundCenter);
		const auto& scaleMat = glm::scale(Mat(1.0), scale3D);// Scaling
		const auto& translateMat2 = glm::translate(Mat(1.0), aroundCenter);
		point = translateMat2 * scaleMat * translateMat * Vec4(point, 1.0);
	}

	/** @note If camera has only translation, no rotation and scale, Model's InverseTransposeMatrix equals Model-View's InverseTransposeMatrix */
	/*const Mat& mvInverseTransposeMatrix(const Mat& viewMatrix = Mat(1.0)) {
		if (Mat3(viewMatrix) == Mat3(1.0))
			return glm::transpose(glm::inverse(modelMatrix()));
		else
			return glm::transpose(glm::inverse(viewMatrix * modelMatrix()));
	}
	void setMVInvTrMatAsUniform(Uniform& uniform, const Mat& viewMatrix = Mat(1.0)) { uniform.setParameter(Enum_toStr(GLSLVarName::MVInvTr), mvInverseTransposeMatrix(viewMatrix)); }*/

	void setPosition(const Vec3& position) { m_pos = position; m_isDirty = true; }
	void setRotation(const Vec3& rotation) { m_eulerRot = rotation; m_isDirty = true; }
	void setRotationY(float rotationY) { m_eulerRot = Vec3(0.0f, rotationY, 0.0f); m_isDirty = true; }
	void setScale(float scale) { setScale(scale, scale, scale); }
	void setScale(float sx, float sy, float sz) { m_scale = Vec3(sx, sy, sz); m_isDirty = true; }

	void setGlobalPos(const Vec3& position) { m_globalPos = position; m_isDirty = true; m_isGlobalDirty = true; }
	void setGlobalRotY(float eulerRotY, const Vec3& rawCenter = Vec3(0.0)) {
		if (rawCenter == Vec3(0.0)) {
			m_globalEulerRot = Vec3(0.0f, eulerRotY, 0.0f);
			m_isGlobalDirty = true;
		} else {
			m_globalRotAroundMatrix = getMatOfRotAroundCenter(rawCenter, eulerRotY);
			m_isGlobalRotAroundDirty = true;
		}
	}
	void setGlobalScale(float scale) { setGlobalScale(scale, scale, scale); }
	void setGlobalScale(float sx, float sy, float sz) { m_globalScale = Vec3(sx, sy, sz); m_isGlobalDirty = true; }

	const Vec3& globalPos() { return m_globalPos; }
	const Vec3& globalScale() { return m_globalScale; }

	/****************************** Global Needs: Generally, global == local ****************************/
	/**
	* @brief The first three columns of the model matrix represent the right, top, and back direction vectors of the object, respectively
	* Model Matrix's structure:
	* |Right.x Up.x Backward.x Position.x|
	* |Right.y Up.y Backward.y Position.y|
	* |Right.z Up.z Backward.z Position.z|
	* |0       0    0          1         |
	const Vec3& right() { return modelMatrix()[0]; }
	const Vec3& up() { return modelMatrix()[1]; }
	const Vec3& backward() { return modelMatrix()[2]; }
	const Vec3& forward() { return -modelMatrix()[2]; }
	const Vec3& globalPosition() { return modelMatrix()[3]; }// <=> globalPosition = ModelMatrix * position
	const Vec3& globalScale() { return { glm::length(right()), glm::length(up()), glm::length(backward()) }; }
	*/

private:
	// Local and Global space information
	Vec3 m_pos = Vec3(0), m_globalPos = Vec3(0);
	Vec3 m_eulerRot = Vec3(0), m_globalEulerRot = Vec3(0);// unit: radians
	Vec3 m_scale = Vec3(1), m_globalScale = Vec3(1);

	// Local/Global space information concatenate in matrix
	Mat m_modelMatrix = Mat_Identity;
	Mat m_globalModelMatrix, m_finalModelMatrix;

	Mat m_globalRotAroundMatrix = Mat_Identity;

	Transform* m_parentTransform = nullptr;
	Mat m_modelMatrixWithParent;

	//Mat m_mvInverseTransposeMatrix;

	/** @brief Dirty flag - true: modelMatrix should be re-calculated, false:no need to re-calculate */
	bool m_isDirty = true, m_isGlobalDirty = true, m_isGlobalRotAroundDirty = true;
};